//
// Created by denglibin on 19-10-28.
//

/**
 *1、 select机制中提供了一个数据结构 struct fd_set ，可以理解为一个集合，实际上是一个位图，每一个特定为来标志相应大小文件描述符，
 * 这个集合中存放的是文件描述符(file descriptor)，即文件句柄(也就是位图上的每一位都能与一个打开的文件句柄(文件描述符)建立联系，这个工作由程序员来完成)，
 * 这可以是我们所说的普通意义的文件，当然Unix下任何设备、管道、FIFO等都是文件形式，全部包括在内，所以毫无疑问一个socket就是一个文件，
 * socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作，程序员通过操作4类宏，来完成最fd_set的操作：
(1)、FD_ZERO(fd_set *)  清空一个文件描述符集合；
(2)、FD_SET(int ,fd_set *)将一个文件描述符添加到一个指定的文件描述符集合中；
(3)、FD_CLR(int ,fd_set*)       将一个给定的文件描述符从集合中删除；
(4)、FD_ISSET(int ,fd_set* )检查集合中指定的文件描述符是否可以读写。
深入的理解select模型的关键点在于理解fd_set，为了说明方便，我们取fd_set长度为1个字节，fd_set中的每一个bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。
（1）执行fd_set set；FD_ZERO(&set)；则set用位表示为 0000,0000 。
（2）若fd = 5 ，则执行 FD_SET(fd,&set)后，set变为 0001，0000 （第5位置为1）
（3）若再加入fd=2 ，fd=1，则set变为 0001.0011
（4）执行select(6,&set,0,0,0)阻塞等待
（5）若fd=1,fd=2上都发生可读事件，则select返回，此时set变为0000，0011。没有可读事件发生时 fd = 5 被清空。
2、struct timeval，一个大家常用的结构，用来代表时间值，有两个成员，一个是秒数，另一个是毫秒数

 3、
 *int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
具体解释select的参数：

（1）int maxfdp是一个整数值，是指集合中所有文件描述符的范围，即所有文件描述符的最大值加1，不能错。
说明：对于这个原理的解释可以看上边fd_set的详细解释，fd_set是以位图的形式来存储这些文件描述符。
 maxfdp也就是定义了位图中有效的位的个数。

 （2）fd_set *readfds是指向fd_set结构的指针，这个集合中应该包括文件描述符，我们是要监视这些文件描述符的读变化的，
 即我们关心是否可以从这些文件中读取数据了，如果这个集合中有一个文件可读，select就会返回一个大于0的值，表示有文件可读；
 如果没有可读的文件，则根据timeout参数再判断是否超时，若超出timeout的时间，select返回0，若发生错误返回负值。
 可以传入NULL值，表示不关心任何文件的读变化。

 （3）fd_set *writefds是指向fd_set结构的指针，这个集合中应该包括文件描述符，我们是要监视这些文件描述符的写变化的，
 即我们关心是否可以向这些文件中写入数据了，如果这个集合中有一个文件可写，select就会返回一个大于0的值，表示有文件可写，
 如果没有可写的文件，则根据timeout参数再判断是否超时，若超出timeout的时间，select返回0，若发生错误返回负值。
 可以传入NULL值，表示不关心任何文件的写变化。

 （4）fd_set *errorfds同上面两个参数的意图，用来监视文件错误异常文件。

 （5）struct timeval* timeout是select的超时时间，这个参数至关重要，它可以使select处于三种状态，
 第一，若将NULL以形参传入，即不传入时间结构，就是将select置于阻塞状态，一定等到监视文件描述符集合中某个文件描述符发生变化为止；
 第二，若将时间值设为0秒0毫秒，就变成一个纯粹的非阻塞函数，不管文件描述符是否有变化，都立刻返回继续执行，文件无变化返回0，有变化返回一个正值；
 第三，timeout的值大于0，这就是等待的超时时间，即 select在timeout时间内阻塞，超时时间之内有事件到来就返回了，否则在超时后不管怎样一定返回，返回值同上述。

 函数返回：

（1）当监视的相应的文件描述符集中满足条件时，比如说读文件描述符集中有数据到来时，内核(I/O)根据状态修改文件描述符集，并返回一个大于0 的数,表示有几个文件描述符就绪。
（2）当没有满足条件的文件描述符，且设置的timeval 监控时间超时时，select函数会返回一个为0的值。
（3）当select返回负值时，发生错误。

 **/

#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>
#include "wrap.h"

#define  MAX_LINE  4096
#define SERVER_PORT 8888

static void start_server(){
    int n_ready, // select返回值 表示就绪的文件描述符数量
    client[FD_SETSIZE]; // 代表客户端的文件描述符数组 /*FD_SETSIZE默认值1024*/
    int i, max_i, // i:用于遍历 client, max_i: client中 最多有多少个客户端
    max_fd, // 最大文件描述符
    listen_fd, // socket函数返回值 代表服务端的socket
    conn_fd, // accept函数返回值，接收连接上的客户端的文件描述符
    sock_fd; // 用于遍历 client
    ssize_t  n;
    fd_set read_set, all_set; //文件描述符集合
    char buf[MAX_LINE];
    char str[INET_ADDRSTRLEN];
    socklen_t  client_addr_len;
    struct sockaddr_in client_addr, servaddr;

    listen_fd = Socket(AF_INET, SOCK_STREAM, IPPROTO_IP);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERVER_PORT);

    Bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    /*默认最大128*/
    Listen(listen_fd, 20);

    max_fd = listen_fd;

    /*初始化*/
    max_i = -1;
    for(i = 0; i < FD_SETSIZE; i++){
        client[i] = -1;
    }
    FD_ZERO(&all_set); // 集合置0 清空
    FD_SET(listen_fd, &all_set); // listen_fd添加到集合中
    printf("select, waiting for connection...\n");
    for(;;){
        read_set = all_set;
        /*每次循环都重新设置select监控信号集，max_fd需要监控的最大文件描述*/
        n_ready = select(max_fd + 1, &read_set, NULL, NULL, NULL);
        if(n_ready < 0){
            perror("select:");
            exit(-1);
        }
        printf("select return %d\n", n_ready);
        /*是否就绪*/
        if(FD_ISSET(listen_fd, &read_set)){ //是否有新的连接到达
            client_addr_len = sizeof(client_addr);
            // 获取连接（因为已经有连接到达了，所以不会阻塞）
            conn_fd = Accept(listen_fd, (struct scokaddr *) &client_addr, &client_addr_len);
            printf("new connection %s:%d\n",
                    inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)),
                    ntohs(client_addr.sin_port));
            for(i = 0; i < FD_SETSIZE; i++){
                if(client[i] < 0){
                    client[i] = conn_fd;/*保存客户端的fd*/
                    break;
                }
            }
            if(i == FD_SETSIZE){ // 连接数太多了
                fputs("too many clients\n", stderr);
                exit(-1);
            }
            FD_SET(conn_fd, &all_set); // 添加到要监控的集合中
            if(conn_fd > max_fd){
                max_fd = conn_fd; // 更新最大文件描述符
            }
            if(i > max_i){ // 更新max_i
                max_i = i;
            }
            if(--n_ready == 0){ // 就绪的文件描述符已经遍历完了
                continue; // 继续下一次循环
            }
        }

        // 客户端的文件描述符有就绪
        for(i = 0; i <= max_i; i++){
            sock_fd = client[i];
            if(sock_fd== -1){ // 该客户端已经断开连接
                continue;
            }
            if(FD_ISSET(sock_fd, &read_set)){ // 该客户端 就绪
                if((n = recv(sock_fd, buf, MAX_LINE, 0)) <= 0){ // 客户端断开连接
                    Close(sock_fd);
                    FD_CLR(sock_fd, &all_set); // 从集合中移除
                    client[i] = -1; // 客户端标示设为-1 表示已经断开
                    printf("client disconnected\n");
                } else { // 转大写然后发送给客户端
                    int j;
                    for(j = 0; j < n; j++){
                        buf[j] = (char)toupper((int)buf[j]);
                    }
                    send(sock_fd, buf, n, 0);
                }
                if(--n_ready == 0){ // 就绪的文件描述符已经遍历完了
                    break;
                }

            }
        }
    }
   // close(listen_fd);
}

static int main_(void){

    start_server();
    return 0;
}

